extern crate reqwest;
extern crate tempfile;

use log::{error, info};
use std::fs;
use std::fs::File;
use std::io;
use std::path;
use std::str::FromStr;

use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::header::{CONTENT_LENGTH, RANGE};
use reqwest::StatusCode;
use std::io::{Read, Write};
use tempfile::Builder;

use super::utils::OutErr;

pub fn copy_with_progress<R: ?Sized, W: ?Sized>(
    progress: &ProgressBar,
    reader: &mut R,
    writer: &mut W,
) -> io::Result<u64>
where
    R: Read,
    W: Write,
{
    let mut buf = [0; 16384];
    let mut written = 0;
    loop {
        let len = match reader.read(&mut buf) {
            Ok(0) => return Ok(written),
            Ok(len) => len,
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
            Err(e) => return Err(e),
        };
        writer.write_all(&buf[..len])?;
        written += len as u64;
        progress.inc(len as u64);
    }
}

fn download(url: &str, dst: &str) -> OutErr<i32> {
    const CHUNK_SIZE: u64 = 10240;

    let client = reqwest::Client::new();
    let response = client.head(url).send()?;
    let length = response
        .headers()
        .get(CONTENT_LENGTH)
        .ok_or("response doesn't include the content length")?;
    let length = u64::from_str(length.to_str()?).map_err(|_| "invalid Content-Length header")?;

    let mut output_file = File::create(dst)?;
    let pb = ProgressBar::new(length);
    pb.set_style(
        ProgressStyle::default_bar()
            .template(
                "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/files ({eta})",
            )
            .progress_chars("#>-"),
    );

    // println!("starting download...");
    for range in 0..(length - 1 as u64) {
        // println!("range {:?}", range);
        let st = range * CHUNK_SIZE;
        let mut response = client.get(url).header(RANGE, st).send()?;

        let status = response.status();
        if !(status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT) {
            error!("Unexpected server response: {}", status);
            panic!("can not to download{}", url.red())
        }

        // std::io::copy(&mut response, &mut output_file)?;
        copy_with_progress(&pb, &mut response, &mut output_file)?;
    }
    Ok(0)
}

fn download_unzip(url: &str, dst: &str) -> OutErr<i32> {
    let tmp_dir = Builder::new().prefix("example").tempdir()?;
    let output: String;

    let fname = url.split("/").last().unwrap();
    info!("file to download: '{}'", fname);
    output = tmp_dir.path().join(fname).to_str().unwrap().to_string();
    // File::create(&output)?
    let out_code = download(url, &output)?;
    if out_code == 0 {
        info!("file extact : {}", &output.green());
        return unzip(&output, dst);
    }

    Ok(1)
}

fn unzip(src: &str, dst: &str) -> OutErr<i32> {
    // let args: Vec<_> = std::env::args().collect();

    let fname = path::Path::new(src);
    let file = fs::File::open(&fname)?;

    let mut archive = zip::ZipArchive::new(file)?;
    let dist_dir = path::Path::new(dst);

    for i in 0..archive.len() {
        let mut file = match archive.by_index(i) {
            Ok(e) => e,
            Err(a) => {
                error!(" extract index error | reason : {}", a);
                continue;
            }
        };
        let outpath = file.sanitized_name();
        let outpath_r = dist_dir.join(outpath);

        {
            let comment = file.comment();
            if !comment.is_empty() {
                println!("File {} comment: {}", i, comment);
            }
        }

        if (&*file.name()).ends_with('/') {
            info!(
                "|{}| {}",
                "zip".green(),
                format!(
                    "File {} extracted to \"{}\" ",
                    i,
                    outpath_r.as_path().display()
                )
                .yellow()
            );
            match fs::create_dir_all(&outpath_r) {
                Ok(_) => {}
                Err(a) => {
                    error!("create dir failed {}", a);
                    continue;
                }
            };
        } else {
            info!(
                "|{}| {}",
                "zip".green(),
                format!(
                    "File {} extracted to \"{}\" ({} bytes)",
                    i,
                    outpath_r.as_path().display(),
                    file.size()
                )
                .yellow()
            );
            if let Some(p) = outpath_r.parent() {
                if !p.exists() {
                    fs::create_dir_all(&p).unwrap();
                }
            }
            let mut outfile = fs::File::create(&outpath_r).unwrap();
            io::copy(&mut file, &mut outfile).unwrap();
        }

        // Get and Set permissions
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;

            if let Some(mode) = file.unix_mode() {
                fs::set_permissions(&outpath_r, fs::Permissions::from_mode(mode)).unwrap();
            }
        }
    }

    Ok(0)
}
